<script>
  import $ from '../dfish.js'
  import {
    h
  } from 'vue'
  import FormBase from './FormBase.vue'
  import Dialog from './Dialog.vue'


  export default {
    name: 'w-linkbox',
    mixins: [FormBase],
    emits: ['input', 'dblclick:item'],
    props: ['multiple', 'picker', 'dropWidth', 'separator', 'strict', 'br'],
    created() {
      this.$watch(() => this.x.modelValue, (v) => {
        if (this.isEntity && !this.isFocus) {
          this.initOptions(v)
        }
      })
    },
    data() {
      return {
        hasBorder: true,
        text: '',
        sugger: null,
        showSuggest: false,
        sug_timer: null,
        _KC: null,
        imeMode: false,
        paste_rng: null,
        currOpt: null,
        store: [],
      }
    },
    methods: {
      x_default() {
        return {
          strict: true
        }
      },
      initOptions(v) {
        if (v == null) v = this.x.modelValue
        if (v && typeof v !== 'string') {
          v = $.array(v)
          this.addStore(v)
          this.inp().innerHTML = this.html_options(v)
        }
      },
      addVal(v) {
        let mv
        if (this.x.multiple) {
          mv = $.lodash.unionBy($.array(this.x.modelValue), $.array(v), 'value')
        } else {
          mv = [v]
        }
        this.val(mv)
      },
      complete(a) {
        this.addStore(a)
        this.focus()
        let u = this.currOpt || $.rngElement()
        let p = this.x.separator || ','
        if (u) {
          if (u.tagName === 'U') {
            this.rng_text(u, 0, a.text)
          } else {
            if (u.tagName === 'I') {
              if (u.nextSibling && u.nextSibling.tagName === 'U') {
                u = u.nextSibling
              } else {
                u = $.after(u, '<u></u>')
                this.x.multiple && $.after(u, '<i>' + p + '</i>')
              }
            } else
              u = (!this.x.multiple && this.inp().querySelector('u')) || $.append(this.inp(), '<u></u>')
            this.rng_text(u, 0, a.text)
          }
          $.classAdd(u, '_o')
          u.setAttribute('data-value', a.value)
          a.forbid && u.setAttribute('data-forbid', '')
          let n = u.nextSibling
          $.rngCursor(n && !n.nextSibling ? n : u)
          this.x.multiple && !n && $.rngCursor($.after(u, '<i>' + p + '</i>')) // 光标在末尾则自动添加逗号分隔符
          let m = $.q(`u[data-value="${a.value}"]`, this.inp())
          if (m.length > 1) m.eq(0).next('i').addBack().remove()
          this.save()
        }
        return this
      },
      closeDrop() {
        this.suggest(false)
        return this
      },
      suggest(a) {
        a = a == null ? true : a
        if (a === false) {
          clearTimeout(this.sug_timer)
        }
        this.showSuggest = a
        this.warn(false)
      },
      input(a) {
        this.currOpt = a
        let v = a.innerText.replace(/^[\s,]+|[\s,]+$/g, '')
        clearTimeout(this.sug_timer)
        if (v == '') {
          this.suggest(false)
        } else {
          this.sug_timer = setTimeout(() => {
            this.suggest(true)
            this.trigger(new Event('input'), v)
          }, 500)
        }
      },
      focus() {
        this.$refs.inp.focus()
        this.warn(false)
        return this
      },
      inp() {
        return this.$refs.inp
      },
      fixStyle() {
        let c = $.rngElement()
        let g = false
        let t
        let uid = this.uid
        let inp = this.inp()
        if (!inp.contains(c))
          c = inp
        t = c.innerText
        if (this.paste_rng)
          c = this.paste_rng.parentElement()
        if (c.id === uid)
          c = inp
        if (c.id === uid + 't') {
          if (t) {
            if (c.firstChild.nodeType === 3) {
              let r = $.rngCursor(c)
              let u = document.createElement('u')
              r.extractContents()
              u.appendChild(document.createTextNode(t))
              r.insertNode(u)
              $.rngCursor(u)
              c = u
            } else {
              $.q('br', inp).remove()
              if (c.firstChild)
                c = c.firstChild
              else
                $.rngCursor(c)
            }
          } else
            return this.closeDrop()
        }
        $.q('br', inp).remove()
        // 输入框内之只允许存在U和I两种标签。输入过程中浏览器可能会自动生成 <font> 标签，需要去除它
        if (c.tagName === 'FONT') {
          if (c.parentNode.tagName === 'U') {
            let d = c.previousSibling.nodeValue
            let c = c.parentNode
            let t = c.innerText
            this.rng_text(c, t.indexOf(d) + d.length + 1, t)
          } else if (c.parentNode.id === uid + 't') {
            t = c.parentNode.innerText
            c = $.replaceWith(c, '<u></u>')
            c.innerText = t
          }
        }

        // 如果U标签后面跟的标签不是 <i>,</i> 就要合并
        if (c !== inp && c.tagName !== 'I' && c.nextSibling && c.nextSibling.tagName !== 'I') {
          if (c.nextSibling.nodeType === 3) {
            c.nextSibling.parentNode.removeChild(c.nextSibling)
          } else {
            t = c.innerText
            this.rng_text(c, t.length, t + c.nextSibling.innerText)
            c.nextSibling.parentNode.removeChild(c.nextSibling)
            g = true // 有更新
          }
        }
        $.q('u:has(br)', inp).remove()
        t = c.innerText
        // match text
        let n = []
        let p = this.x.separator || ','

        // 文本中如果包含分隔符 , 则分成若干选项
        if (this.x.multiple && t.length > 1 && t.indexOf(p) > -1) {
          let d = [],
            f = t.length - $.rngCursorOffset(),
            h, m = []
          t.split(p).forEach(s => {
            if (h = $.strTrim(s)) {
              g = this.param(h)
              d.push('<u' + (g ? ' class=_o data-value="' + g.value + '"' + (g.forbid ? ' data-forbid' : '') : '') +
                '>' + h + '</u>')
              g ? m.push('u[data-value="' + g.value + '"]') : n.push(h)
            } else
              d.push('')
          })
          m.length && (m = $.q(m.join(','), inp).next('i').addBack()) // 收集重复数据，以备删除
          let o = $.replaceWith(c, d.join('<i>' + p + '</i>'))
          let k = 0
          if (!this.paste_rng || this._KC === 188) { // 保留原有光标位置
            do {
              if ((k += o.innerText.length) >= f) {
                this.rng_text(o, k - f)
                break
              }
            } while (o = o.previousSibling)
          }
          m.length && (m.remove(), $.q('i + i, i:first-child', inp).remove()) // 删除重复数据
          g = true
          if ((c = $.rngElement()) && c.tagName === 'U' && !c.getAttribute('data-value'))
            this.input(c)
          else
            this.closeDrop()
        } else {
          if (t) {
            if ($.classAny(c, '_o') && !this.param(t)) {
              $.classRemove(c, '_o')
              c.removeAttribute('data-value')
              g = true
            } else if (!this.x.strict)
              g = true
            this.input(c)
          } else {
            g = true
            this.closeDrop()
          }
        }
        if (g) {
          this.save()
          //this._online && n.length > 1 && this.match({text: n.join(p)})
        }
        this.paste_rng = null
      },
      // 选项上的光标移动到指定位置 /@a -> elem, b -> cursor point, c -> text?
      rng_text(a, b, c) {
        let n = $.rngSelection()
        c != null && (a.innerText = c)
        n.selectNodeContents(a)
        $.rngCursor(n, b)
      },
      clickPicker(e) {
        e.stopPropagation()
        let c = this.x.picker
        if (typeof c === 'function') c(this)
        else this.cmd(c, this.formVal())
      },
      clickDropper(e) {
        e.stopPropagation()
        this.suggest(true)
        let c = this.x.dropper
        if (typeof c === 'function') c(this)
        else this.cmd(c, this.formVal())
      },
      addStore(m) {
        if (m && typeof m !== 'string') {
          let r = $.array(m).filter(v => !this.store.find(s => s.value == v.value))
          if (r.length) this.store = this.store.concat(r)
        }
      },
      param(t) {
        return this.store.find(v => v.text == t)
      },
      save() {
        let strict = this.x.strict
        let r = []
        let m = []
        $.q('> u', this.inp()).each((i, u) => {
          let value = u.getAttribute('data-value')
          let text = u.innerText
          if (value) {
            r.push(value)
            m.push({
              value,
              text
            })
          } else if (!strict) {
            r.push(text)
            m.push({
              value: text
            })
          }
        })
        //this.$refs.v.value = r.join(',')
        this.val(m)
      },
      onkeydown(e) {
        let k = e.keyCode
        if (k === 13) {
          e.preventDefault()
        }
      },
      onkeyup(e) {
        if (!this.imeMode) {
          let k = this._KC = e.keyCode
          let m
          if (k === 13 || k === 38 || k === 40 || k === 17) { // 38:up, 40:down, 17:ctrl
            e.stopPropagation()
          } else {
            this.fixStyle()
          }
          this._KC = null
        }
      },
      onclick(e) {
        if (this.isNormal) {
          if (e.target && e.target.tagName === 'U' && !e.target.getAttribute('data-value')) {
            this.input(e.target)
          } else {
            let c = $.rngElement()
            c.tagName === 'U' ? this.input(c) : this.closeDrop()
          }
        }
      },
      ondblclick(e) {
        if (e.target.tagName === 'U' && e.target.getAttribute('data-value')) {
          let v = $.array(this.x.modelValue).find(o => o.value == e.target.getAttribute('data-value'))
          if (v) {
            e.runType = 'dblclick:item'
            this.trigger(e, v)
            window.getSelection().removeAllRanges()
          }
        }
      },
      formText() {
        return this.input_title
      },
      html_options(v) {
        let p = this.x.separator || ','
        if (v == null) v = this.x.modelValue
        if (v !== 'string') {
          let b = $.array(v)
          let r = []
          $.lodash.each(b, (o, i) => {
            r.push('<u' + (o.value ? ' class=_o data-value="' + o.value + '"' : '') + (o.forbid ? ' data-forbid' :
              '') + '>' + o.text + '</u>')
            if (!this.x.multiple) return false
            r.push('<i>' + p + '</i>')
          })
          return r.join('')
        }
      },
      html_var() {
        return h('div', {
          class: 'w-linkbox-input f-flex-1',
          ref: 'inp',
          id: this.uid + 't',
          contentEditable: this.isNormal,
          onkeyup: this.onkeyup,
          onkeydown: this.onkeydown,
          onclick: this.onclick,
          ondblclick: this.ondblclick,
          onfocus: e => {
            this.isFocus = true
          },
          onblur: e => {
            this.isFocus = false
          },
        })
      },
      html_input() {
        let p = this.input_placeholder
        return [
          this.html_input_hidden(),
          this.html_var(),
          h('div', {
            class: 'w-input-placeholder'
          }, this.input_value ? null : p),
          this.showSuggest && h(Dialog, {
            context: this,
            ownproperty: true,
            class: 'f-dfish-dialog',
            width: Math.max($.number(this.x.dropWidth), $.bcr(this.el('main')).width),
            scrollY: true,
            autoHide: true,
            snap: {
              target: () => this.el('main'),
              position: 'v',
              indent: 1
            },
            on: {
              close: () => this.showSuggest = false
            }
          }, this.$slots)
        ]
      },
      html_button() {
        let s = []
        if (this.x.dropper) {
          s.push(h('button', {
            class: 'w-input-bbr f-i f-i-caret-down',
            disabled: !this.isNormal,
            onclick: this.clickDropper
          }))
        }
        if (this.x.picker) {
          s.push(h('button', {
            class: 'w-input-bbr f-i f-i-ellipsis',
            disabled: !this.isNormal,
            onclick: this.clickPicker
          }))
        }
        return s
      }
    },
    computed: {
      validHooks() {
        return {
          ...this.pubValidHooks,
          validateDefault: (v) => {
            if (this.x.strict && $.q('u:not([data-value])').length > 0) {
              return this.formErr({type: 'invalid_option'})
            }
          }
        }
      },
      input_value() {
        return typeof this.x.modelValue === 'string' ? this.x.modelValue : $.array(this.x.modelValue).map(v => v.value)
          .join(',')
      },
      input_title() {
        return typeof this.x.modelValue === 'string' ? '' : $.array(this.x.modelValue).map(v => v.text || v.value).join(
          ', ')
      },
      main_prop() {
        return {
          onclick: this.isNormal && this.focus
        }
      },
      html_class() {
        return {
          'z-br': this.x.br,
          'z-dc': this.getOn('dblclick:item')
        }
      }
    },
    mounted() {
      if (this.isEntity) {
        this.initOptions()
        this.inp().addEventListener('compositionstart', () => {
          this.imeMode = true
        })
        this.inp().addEventListener('compositionend', () => {
          this.imeMode = false
        })
      }
    }
  }
</script>

<style>

  .w-linkbox-input {
    height: 100%;
    background: transparent;
    border: 0;
  }

  .w-linkbox .w-linkbox-input {
    padding-left: 10px;
    min-height: 30px;
    line-height: 30px;
    font-family: verdana;
    font-style: normal;
    outline: none;
    overflow: hidden;
    word-break: keep-all;
    word-wrap: normal;
  }

  .w-linkbox .w-linkbox-input u {
    padding-right: 1px;
    color: #999;
    text-decoration: none
  }

  .w-linkbox .w-linkbox-input u._o {
    color: #333
  }

  .w-linkbox .w-linkbox-input u._o[data-forbid] {
    text-decoration: line-through !important;
  }

  .w-linkbox.z-dc .w-linkbox-input u {
    color: #333
  }

  .w-linkbox.z-dc .w-linkbox-input u._o {
    color: #275696;
    text-decoration: underline
  }

  .w-linkbox .w-linkbox-input i {
    font-style: normal;
    padding: 0 3px 0 1px;
    color: #999;
  }

  .w-linkbox ._v {
    display: inline-block;
    height: 22px;
    line-height: 22px;
    vertical-align: middle
  }

  .w-linkbox.z-loading {
    color: gray;
  }

  .w-linkbox.z-loading .w-input-placeholder {
    display: none;
  }

  .w-linkbox.z-br .w-linkbox-input {
    overflow: visible;
    word-break: break-all;
    word-wrap: break-word;
  }

  .w-linkbox .w-input {
    position: relative;
  }

  .w-linkbox-input:not(:empty)~.w-input-placeholder {
    display: none;
  }

  .w-linkbox .w-input-placeholder {
    position: absolute;
    left: 0;
    padding-left: 10px;
    pointer-events: none;
  }
</style>
